package doc

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

//any replacement need to achieve replace method
type rule interface {
	//接口不能定义变量
	//pattern string
	//replace FUNC
	replace(string) string
}

//generate regexp matching attribute
func AttrRegExp(attr string) *regexp.Regexp {
	// \s 是任意空白符
	return regexp.MustCompile(attr + `\s*=\s*["']?([^"\"']*)["']?`)
}

//p rule
//对p进行转换
//<p>Hello</p> -> \n\nHello\n
type p struct {
}

func (p *p) replace(html string) string {
	//匹配p标签
	re, err := regexp.Compile("<p\\b([^>]*)>([\\s\\S]*?)<\\/p>")
	if err != nil {
		panic(err)
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		matches := re.FindStringSubmatch(match)
		return "\n\n" + matches[2] + "\n"
	})
	return converted
}

//b rule
//<b>hello</>->**hello**
type b struct {
}

func (b *b) replace(html string) string {
	re, err := regexp.Compile("<(b|strong)\\b([^>]*)>([\\s\\S]*?)<\\/(b|strong)>")
	if err != nil {
		fmt.Println(err)
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		matches := re.FindStringSubmatch(match)
		if len(matches) > 3 {
			return "**" + matches[3] + "**"
		}
		return ""
	})
	return converted
}

//i rule
//<i>hello</i> -> _hello_
type i struct {
}

func (i *i) replace(html string) string {
	re, err := regexp.Compile("<(i|em)\\b([^>]*)>([\\s\\S]*?)<\\/(i|em)>")
	if err != nil {
		fmt.Println(err)
		return ""
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		matches := re.FindStringSubmatch(match)
		if len(matches) > 3 {
			return "_" + matches[3] + "_"
		}
		return ""
	})
	return converted
}

//code rule
//<code>fmt.println(`helllo`)</code> -> `fmt.println(`helllo`)`
type code struct {
}

func (code *code) replace(html string) string {
	re, err := regexp.Compile("<code\\b([^>]*)>([\\s\\S]*?)<\\/code>")
	if err != nil {
		fmt.Println(err)
		return ""
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		matches := re.FindStringSubmatch(match)
		if len(matches) > 2 {
			return "`" + matches[2] + "`"
		}
		return ""
	})
	return converted
}

//br urle
//br -> \n\n
type br struct {
}

func (br *br) replace(html string) string {
	re, err := regexp.Compile("<br\\b[^>]*\\/?>")
	if err != nil {
		panic(err)
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		return "\n\n"
	})
	return converted
}

//h
//<hN>hello</hN> -> \n\nN#hello\n
type h struct {
}

func (h *h) replace(html string) string {
	re, err := regexp.Compile("<h([1-6])\\b([^>]*)>([\\s\\S]*?)<\\/h[1-6]>")
	if err != nil {
		panic(err)
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		matches := re.FindStringSubmatch(match)
		hLevel, err := strconv.Atoi(matches[1])
		if err != nil {
			fmt.Println(err)
			return ""
		}
		return "\n\n" + strings.Repeat("#", hLevel) + " " + matches[3] + "\n"
	})
	return converted
}

//horizon rule
//hr -> \n\n***\n
type hr struct {
}

func (ht *hr) replace(html string) string {
	re, err := regexp.Compile("<hr\\b[^>]*\\/?>")
	if err != nil {
		fmt.Println(err)
		return ""
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		return "\n\n* * *\n"
	})
	return converted
}

//a rule
// <a href="/index">index</a> -> [index]("/index")
// if there is no text in a , return ""
type a struct {
}

func (a *a) replace(html string) string {
	re := regexp.MustCompile("<a\\b([^>]*)>([\\s\\S]*?)<\\/a>")
	//test if there is nothing in anchor text
	noBody := regexp.MustCompile(`^\s+$`)
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		hrefReg := AttrRegExp("href")
		var href string
		hrefs := hrefReg.FindStringSubmatch(match)
		if len(hrefs) > 1 {

			href = hrefs[1]
		}
		matches := re.FindStringSubmatch(match)
		if len(matches) > 2 {
			if !noBody.MatchString(matches[2]) {
				return "[" + matches[2] + "](" + href + ")"
			} else {
				return ""
			}
		}
		return ""
	})
	return converted
}

//img rule
//<img src="src.jpg" alt="alt_text" title+"title_text"/> -> ![alt_text]("src.jpg" "title_text")
//if alt is empty string use text "image" instead
//title is optional
//if src is empty its ok
type img struct {
}

func (img *img) replace(html string) string {
	re, err := regexp.Compile("<img\\b[^>]*\\/?>")
	if err != nil {
		fmt.Println(err)
		return ""
	}
	converted := re.ReplaceAllStringFunc(html, func(match string) string {
		var src, alt, title string
		srcReg := AttrRegExp("src")
		altReg := AttrRegExp("alt")
		titleReg := AttrRegExp("title")

		srcs := srcReg.FindStringSubmatch(match)
		if len(srcs) > 1 {
			src = srcs[0]
		}
		titles := titleReg.FindStringSubmatch(match)
		if len(titles) > 1 {
			title = titles[1]
		}
		alts := altReg.FindStringSubmatch(match)
		if len(alts) > 1 {
			alt = alts[1]
		}
		if len(title) > 0 {
			if len(alt) == 0 {
				alt = title
			}
			return "![" + alt + "](" + src + " \"" + title + "\")"
		}
		if len(alt) == 0 {
			alt = "image"
		}
		return "![" + alt + "](" + src + ")"
	})
	return converted
}

//TODO:convert table
type table struct {
}

func (t *table) replace(html string) string {
	re := regexp.MustCompile(`<table\b(?:[^>]*)>([\s\S]*?)<\/table>`)
	//match heading and trailing tbody and thead, but do nothing with them, just remove
	reTr := regexp.MustCompile(`(?:\s*(?:<tbody>|<thead>))?\s*<tr\b(?:[^>]*)>([\s\S]*?)<\/tr>(?:\s*?(<\/tbody>|<\/thead>)\s*)?`)
	reTd := regexp.MustCompile(`<td\b(?:[^>]*)>([\s\S]*)`)
	reTh := regexp.MustCompile(`<th\b(?:[^>]*)>([\s\S]*)`)
	//convert table
	html = re.ReplaceAllStringFunc(html, func(match string) string {
		matches := re.FindStringSubmatch(match)
		//convert tr in table
		converted := reTr.ReplaceAllStringFunc(matches[1], func(match string) string {
			matches := reTr.FindStringSubmatch(match)
			//convert th in tr
			if reTh.MatchString(matches[1]) {
				var newThs []string
				var ths []string
				ths = strings.Split(matches[1], `</th>`)
				for i := 0; i < len(ths)-1; i++ {
					matches := reTh.FindStringSubmatch(ths[i])
					newThs = append(newThs, matches[1])
				}
				count := len(newThs)
				header := strings.Join(newThs, "|")
				lines := make([]string, count)
				for i := 0; i < count; i++ {
					lines[i] = "---"
				}
				line := strings.Join(lines, "|")
				return header + "\n" + line + "\n"
			}
			//convert td in tr
			if reTd.MatchString(matches[1]) {
				var newTds []string
				var tds []string
				tds = strings.Split(matches[1], `</td>`)
				for i := 0; i < len(tds)-1; i++ {
					matches := reTd.FindStringSubmatch(tds[i])
					newTds = append(newTds, matches[1])
				}
				data := strings.Join(newTds, "|")
				return data + "\n"
			}
			return match
		})
		return "\n\n" + converted + "\n"
	})
	return html
}

/* rule

<ol>
	<li>
		Hello			1.  Hello
	</li>			-> 	2.  World
	<li>
		World
	</li>
</ol>
nested list
<ol>
	<li>
		hello
	</li>
	<li>
		hello					->    1.  hello
	</li>						      2.  hello
	<li>						      3. 1.hello
		<ol>
			<li>
				hello
			</li>
		</ol>
	</li>
</ol>
*/
type l struct {
}

func (l *l) replace(html string) string {
	// \1 表示匹配第一分组的内容
	// golang目前不支持,fuck了
	// 用一个比较挫的替代的吧
	//there is no backreference so I use | instead
	re := regexp.MustCompile(`<(ol)\b[^>]*>([\s\S]*?)<\/ol>|<(ul)\b[^>]*>([\s\S]*?)<\/ul>`)

	for re.MatchString(html) {
		html = re.ReplaceAllStringFunc(html, func(inner string) string {
			//match 是传进来的ul ol
			//这段逻辑有点乱,如果是ul的话1,2的匹配是空的
			//子匹配是按顺序来的
			var d int
			if inner[:3] == "<ul" {
				d = 2
			}
			matches := re.FindStringSubmatch(inner)
			tag := matches[1+d]
			match := matches[2+d]
			//there is no (?!:reg)
			//so I use LastIndex instead
			index := strings.LastIndex(match, "<"+tag+">")
			var prev, main string
			if index == -1 {
				main = match
			} else {
				prev = "<" + tag + ">" + match[:index]
				main = match[index+4:]
			}
			var prefix = "*   "
			lis := strings.Split(main, "</li>")
			newList := make([]string, 0)
			for i, li := range lis[:len(lis)-1] {
				if tag == "ol" {
					prefix = fmt.Sprintf("%d.  ", i+1)
				}
				re := regexp.MustCompile(`\s*<li[^>]*>([\s\S]*)`)
				newList = append(newList, re.ReplaceAllStringFunc(li, func(innerHTML string) string {
					matches := re.FindStringSubmatch(innerHTML)
					//先1认为有缩进和没有缩进一样
					//innerHTML = regexp.MustCompile(`\s+$`).ReplaceAllString(matches[2], "")
					innerHTML = regexp.MustCompile(`^\s+`).ReplaceAllString(matches[1], "")
					//子列表的开头缩进一下
					//				innerHTML = regexp.MustCompile(`\n\n`).ReplaceAllString(innerHTML, "\n\n    ")
					//有子列表就缩进一下
					innerHTML = regexp.MustCompile(`\n([ ]*)(\*|\d+\.) `).ReplaceAllString(innerHTML, "\n$1    $2 ")
					return prefix + innerHTML
				}))
			}
			return prev + "\n\n" + strings.Join(newList, "\n") + "\n"
		})
	}
	return html
}

//blockquotes rule
//if nested
//add > >
type blockquotes struct {
}

func replaceBlockquotes(html string) string {
	re := regexp.MustCompile(`<blockquote\b[^>]*>([\s\S]*?)</blockquote>`)
	return re.ReplaceAllStringFunc(html, func(inner string) string {
		matches := re.FindStringSubmatch(inner)
		inner = regexp.MustCompile(`^\s+|\s+$`).ReplaceAllString(matches[1], "")
		//	inner = cleanUp(inner)
		inner = regexp.MustCompile(`(?m)^`).ReplaceAllString(inner, "> ")
		inner = regexp.MustCompile(`(?m)^(>([ \t]{2,}>)+)`).ReplaceAllString(inner, "> >")
		return inner
	})
}

func (bq *blockquotes) replace(html string) string {
	deep := regexp.MustCompile(`<blockquote\b[^>]*>(?:[\s\S]*?)</blockquote>`)
	return deep.ReplaceAllStringFunc(html, func(str string) string {
		return replaceBlockquotes(str)
	})
}

type pre struct {
}

func (pre *pre) replace(html string) string {
	re := regexp.MustCompile(`<pre\b[^>]*>([\s\S]*)</pre>`)
	return re.ReplaceAllStringFunc(html, func(inner string) string {
		matches := re.FindStringSubmatch(inner)
		//这个是什么匹配呀,fuck
		inner = regexp.MustCompile(`(?m)^\t+`).ReplaceAllString(matches[1], "   ")
		inner = regexp.MustCompile(`(?m)\n`).ReplaceAllString(inner, "\n   ")
		return "\n\n   " + inner + "\n"

	})
}

type clean struct {
}

func (c *clean) replace(html string) string {
	html = regexp.MustCompile(`^[\t\r\n]+|[\t\r\n]+$`).ReplaceAllString(html, "") // trim leading/trailing whitespace
	html = regexp.MustCompile(`\n\s+\n`).ReplaceAllString(html, "\n\n")
	html = regexp.MustCompile(`\n{3,}`).ReplaceAllString(html, "\n\n") // limit consecutive linebreaks to 2
	return html
}

var RuleSequence = []rule{
	new(a),
	new(b),
	new(blockquotes),
	new(br),
	new(code),
	new(h),
	new(hr),
	new(i),
	new(img),
	new(p),
	new(pre),
	new(table),
	new(l),
	new(clean),
}

func ToMarkdown(html string) string {

	for _, r := range RuleSequence {
		html = r.replace(html)
	}
	return html
}
